package xj.app;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.swing.AbstractButton; 
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.WindowConstants;

import xj.graph2d.app.DrawApp;
import xj.util.gui.ResourceLoader;
import xj.util.gui.ResourceManager;
import xj.util.gui.SplashScreen;
import xj.util.gui.Utilities;

/**
 * A base GUI application. 
 * Provides the basic application-wide services and resources.
 * There should be one instance of this class only, although it is not enforced as a singleton. 
 */
public class BaseApplicationFrame 
  implements ApplicationFrame {

  public JFrame getJFrame() { 
    return frame;
  }
  
  public void setTitle(String title) { 
    frame.setTitle(title);
  }
  
  //
  //
  //

  public BaseApplicationFrame() {
    frame = new JFrame(); 
    frame.setBackground(Color.lightGray);
    frame.getContentPane().setLayout(new BorderLayout());
  }

  //
  // Basic application wide properties 
  // 

  public String getAppName() {
    return appName;
  }

  public String getAppDir() {
    return appDir;
  }

  public String getDefaultDir() {
    String userHome = System.getProperty("user.home");
    return (userHome + File.separator + appDir);
  }

  public String getVersion() {
    return version;
  }

  public String getBuild() {
    return build;
  }

  public String getRevision() {
    return revision;
  }

  public String getReleaseDate() {
    return releaseDate;
  }
  
  //
  // Namespace for XML files written by the application 
  //

  public void setNamespace(String namespaceURI, String namespacePrefix) {
    this.namespaceURI = namespaceURI;
    this.namespacePrefix = namespacePrefix;
  }

  public String getNamespaceURI() {
    return namespaceURI;
  }

  public String getNamespacePrefix() {
    return namespacePrefix;
  }

  //
  // Geometry 
  //

  public int getFrameWidth() {
    return frameWidth;
  }

  public int getFrameHeight() {
    return frameHeight;
  }

  public void setFrameWidth(int w) {
    frameWidth = w;
  }

  public void setFrameHeight(int h) {
    frameHeight = h;
  }

  //
  // App listener 
  //

  public void setAppListener(AppListener al) { 
    appListener = al;
  }

  public AppListener getAppListener() { 
    return appListener;
  }

  public void stopAutoSave() {
    for (Application app : apps) { 
      app.stopAutoSave(); 
    }
  }

  public void stopThreads() {
    for (Application app : apps) { 
      app.stopThreads(); 
    }
  }

  public void exitAppFrame() {
    boolean doExit = true; 
    if (curApp != null) { 
      doExit = curApp.exitApp(); 
    }
    Iterator<Application> iter = apps.iterator();
    while (doExit && 
	   iter.hasNext()) {
      Application app = iter.next();
      if (app != curApp) { 
	doExit = app.exitApp(); 
      }
    }
    if (doExit) { 
      exit(0); 
    }
  }

  public void setFrame() {    
    frame.setIconImage(drawFrameIcon);
    int width = getFrameWidth();
    int height = getFrameHeight();
    frame.setSize(width, height);
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    int x = screenSize.width / 2 - width / 2;
    int y = screenSize.height / 2 - height / 2;
    frame.setLocation(x, y);
    
    frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
    frame.addWindowListener(new WindowAdapter() {
	  @Override
	  public void windowClosing(WindowEvent event) {
	  exitAppFrame();
	}
      });

    //Container cp = getContentPane(); 
    //cp.repaint();
    frame.setVisible(true);
  }

  public void exit(int code) {
    stopAutoSave();
    stopThreads();
    if (appListener != null) { 
      appListener.exitApp();
    }
    closeLogger();
    System.exit(code);
  }

  public void hourGlassOn() {
    if (hourGlassPane == null) { 
      hourGlassPane = new HourGlassPane(frame); 
    }
    hourGlassPane.start(); 
  }

  public void hourGlassOff() {
    if (hourGlassPane != null) { 
      hourGlassPane.stop(); 
    }
  }


  ///////////////////////////////////////////////
  //
  // Static methods 
  // 
  ///////////////////////////////////////////////

  public static void setAppName(String appName) {
    BaseApplicationFrame.appName = appName;
  }

  public static void setVersion(String version) {
    BaseApplicationFrame.version = version;
  }

  public static void setRevision(String revision) {
    BaseApplicationFrame.revision = revision;
  }

  public static void setBuild(String build) {
    BaseApplicationFrame.build = build;
  }

  public static void setReleaseDate(String releaseDate) {
    BaseApplicationFrame.releaseDate = releaseDate;
  }

  public static void setAppDir(String appDir) {
    BaseApplicationFrame.appDir = appDir;
  }

  //
  // Splash screen 
  //

  protected static String splashIconName = null;
  protected static int splashProgress = 0; 

  public static void createSplashScreen(String iconResourceName) {
    if (iconResourceName == null) { 
      splashIconName = "/icons/splashDraw.jpg"; // default image 
    } else { 
      splashIconName = iconResourceName;
    }

    splashScreen = SplashScreen.getSplashScreen();
    splashScreen.startSplash(appName, 
			     Utilities.getIcon(splashIconName, BaseApplicationFrame.class));
    splashScreen.setProgress(10);
    splashScreen.setText("Initializing " + appName + " ...");
  }

  public static void hideSplashScreen() {
    if (splashScreen != null) {
      splashScreen.dispose();
      splashScreen = null;
    }
  }

  public static void setSplashScreenProgress(int n) { 
    if (splashScreen != null) { 
      if (n < 0) { 
	n = 0;
      }
      if (n > 100) { 
	n = 100;
      }
      splashProgress = n; 
      splashScreen.setProgress(splashProgress);
      //Dimension d = splashScreen.getSize();
      //splashScreen.paintImmediately(0, 0, d.width, d.height); 

      //System.out.println("setSplashScreenProgress() " + n);
    }
  }

  //
  // Resources and locale 
  //

  static public String getResourceName() {
    return BaseApplicationFrame.resourceName;
  }

  static public void setResourceName(String resourceName) {
    BaseApplicationFrame.resourceName = resourceName;
  }

  static public ResourceManager getResourceManager() {
    if (resourceManager == null) {
      if (locale == null) {
	resourceManager = new ResourceManager(resourceName, Locale.getDefault());
      } else {
	resourceManager = new ResourceManager(resourceName, locale);
      }
    }
    return resourceManager;
  }

  static public String getResourceString(String key) { 
    String text = null; 
    if (key != null) { 
      if (resourceManager == null) { 
	getResourceManager();
      }
      text = resourceManager.getResourceString(key); 
      if (text == null) { 
	int i = key.indexOf('.');
	if (i > 0) { 
	  String key2 = "*." + key.substring(i + 1); 
	  text = resourceManager.getResourceString(key2); 
	}
      }
    }
    return text;
  }

  static public Locale getViewLocale() {
    return BaseApplicationFrame.locale;
  }
  static public void setViewLocale() {
    setViewLocale(BaseApplicationFrame.locale);
  }
  
  static public void setViewLocale(Locale locale) {
    if (resourceManager == null || 
	BaseApplicationFrame.locale != locale) {
      BaseApplicationFrame.locale = locale;
      if (locale == null) {
	resourceManager = new ResourceManager(resourceName, Locale.getDefault());
      } else {
	resourceManager = new ResourceManager(resourceName, locale);
      }
    }
  }

  //
  // System properties 
  // 
  public static String getSystemProperty(String name) { 
    if (sysprop != null && 
	name != null) { 
      return sysprop.getProperty(name);
    }
    return null;
  }

  public static String getSystemConfigFile() { 
    return sysConfigFile;
  }

  //
  // OS, Looks & Feel 
  //
  public static boolean isMacOS() { 
    return isMac;
  }

  public static boolean isLinuxOS() { 
    return isLinux;
  }

  public static boolean isWindowsOS() { 
    return isWindows;
  }

  public static boolean isWindowsVistaOS() { 
    return isWindowsVista;
  }

  public static LookAndFeelManager getLookAndFeelManager() {
    return lookAndFeelManager;
  }

  // 
  // Logger 
  //

  public static void startLogger() {
    try {
      InputStream in = 
	BaseApplicationFrame.class.getResourceAsStream("/resources/graph2d/app/version.properties");
      sysprop.load(in);
      in.close();

    } catch (IOException e) {
      e.printStackTrace(System.err);
    }

    String appname, version;
    setAppName(appname = sysprop.getProperty("APPNAME"));
    setVersion(version = sysprop.getProperty("VERSION"));
    setRevision(sysprop.getProperty("REVISION"));
    setBuild(sysprop.getProperty("BUILD"));
    setReleaseDate(sysprop.getProperty("RELEASEDATE"));

    // set appDir
    String s = appname + version;
    StringBuilder sb = new StringBuilder();
    int n = s.length();
    for (int i = 0; i < n; i++) {
      char c = s.charAt(i);
      if (c != ' ') {
	sb.append(c);
      }
    }
    setAppDir(sb.toString());

    try {
      String userhome = System.getProperty("user.home");
      String logdir = userhome + File.separator + appDir + File.separator + LOG_DIR;
      String logfile = logdir + File.separator + LOG_FILE;
      File logd = new File(logdir);
      if (!logd.exists()) {
	logd.mkdirs();
      }
      xmlHandler = new FileHandler(logfile + ".xml");
      simpleHandler = new FileHandler(logfile + ".log");
      simpleHandler.setFormatter(new SimpleFormatter());
    } catch (IOException e) {
      e.printStackTrace(System.err);

      simpleHandler = new ConsoleHandler();
    }

    // log = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); //JDK6
    log = Logger.getLogger("global");

    log.setUseParentHandlers(false);
    if (xmlHandler != null) {
      log.addHandler(xmlHandler);
    }
    if (simpleHandler != null) {
      log.addHandler(simpleHandler);
    }

    if (debug) {
      System.out.println("BaseApplicationFrame.debug: true");
    }

    logInfo("Start log [version " + LOG_VERSION + "]: " + new Date());
    logInfo("AppName: " + appName + "  version: " + version + 
	     "  revision: " + revision + "  build: " + build + 
	     "  release date: " + releaseDate);

    if (debug) { 
      ByteArrayOutputStream sout = new ByteArrayOutputStream(); 
      PrintStream out = new PrintStream(sout);
      sysprop.list(out);
      out.close();
      logInfo(sout.toString());
      //sysprop.list(System.out);
    }

  }

  public static void logInfo(String s) { 
    if (debug) { 
      System.out.println("Info: " + s); 
    }
    log.info(s);
  }

  public static void logError(String s) { 
    if (debug) { 
      System.out.println("Error: " + s); 
    }
    log.severe(s);
  }

  public static void logWarning(String s) { 
    if (debug) { 
      System.out.println("Warning: " + s); 
    }
    log.warning(s);
  }

  public static void closeLogger() {
    log.info("Close log: " + new Date());
    if (xmlHandler != null) {
      xmlHandler.close();
    }
    if (simpleHandler != null) {
      simpleHandler.close();
    }
  }

  //
  // Applications
  // 

  public Application getCurrentApplication() { 
    return curApp; 
  }

  public void addApplication(Application app) { 
    if (app != null) { 
      apps.add(app); 
    }
  }

  public Application getApplication(String name) { 
    if (name != null) { 
      for (Application app : apps) { 
	if (name.equals(app.getName())) { 
	  return app;
	}
      }
    }
    return null;
  }

  public void setApplication(String name) { 
    if (name != null) { 
      Application app = getApplication(name); 
      if (app != null && 
	  app != curApp) { 
	setApplication(app);
      }
    }
  }

  public void setApplication(Application app) { 
    if (app != null && 
	app != curApp) { 
      if (!apps.contains(app)) { 
	apps.add(app); 	
      }

      if (curApp != null) { 
	int mi = curApp.getSwitchAppMenuIndex();
	if (mi >= 0) { 
	  JMenuBar cbar = curApp.getAppMenuBar();	  
	  cbar.remove(mi);
	} 
      }

      curApp = app; 
       
      JMenuBar menubar = app.getAppMenuBar();
      int mi = curApp.getSwitchAppMenuIndex();
      if (menubar != null && 
	  mi >= 0) {
	JMenu menu = makeSwitchAppMenu();
	menubar.add(menu, mi); 
      }

      frame.setJMenuBar(menubar); 
      if (menubar != null) { 
	menubar.revalidate();
	menubar.repaint();
      }

      updateAppUI();
    }
  }

  public void updateAppUI() { 
    if (curApp != null) {        
      JComponent comp = curApp.getAppComponent();
      JComponent toolbar = curApp.getAppToolBar();
      JComponent statusbar = curApp.getAppStatusBar();
      Container cp = frame.getContentPane(); 
      cp.removeAll(); 
      if (comp != null) { 
	cp.add(comp, BorderLayout.CENTER);
	comp.revalidate();
      }      
      if (toolbar != null) {
	cp.add(toolbar, BorderLayout.NORTH);
	toolbar.revalidate();
      }
      if (statusbar != null) {
	cp.add(statusbar, BorderLayout.SOUTH);
	statusbar.revalidate();
      }
      cp.repaint();
    }
  }

  protected ActionListener switchAppAction = null; 

  protected JMenu makeSwitchAppMenu() { 
    if (switchAppAction == null) { 
      switchAppAction = new ActionListener() { 
	  public void actionPerformed(ActionEvent evt) { 
	    if (evt != null) { 
	      Object src = evt.getSource();
	      if (src instanceof AbstractButton) { 
		AbstractButton b= (AbstractButton) src;
		String cmd = b.getActionCommand();
		setApplication(cmd);
	      }
	    }
	  }
	};
    }

    JMenu menu = new JMenu("Switch App");
    for (Application app : apps) { 
      String cmd = app.getName();
      JMenuItem item = new JMenuItem(cmd);
      item.setActionCommand(cmd);
      item.setEnabled(app != curApp);
      item.addActionListener(switchAppAction); 
      menu.add(item);
    }
    return menu;
  }

  //
  // ApplicationFrame launcher 
  //

  public static void initConfig() { 
    initConfig(null);
  }

  public static void initConfig(String res) { 
    if (res == null) { 
      res = "resources.graph2d.app.DrawApp";
    }
    setResourceName(res);
    setViewLocale();
  }

  public static void startApp(String[] args) {
    startApp(args, null);
  }

  public static void startApp(String[] args, 
			      String configRes) {
    startLogger();
    log.info("Start AVM " + new Date());
    
    String lnf = null; 
    for (int i = 0; i < args.length; i++) {
      String arg = args[i].trim();
      if (arg.length() > 0) {
	if (arg.startsWith("-")) {
	  if ("-locale".equals(arg)) {
	    String loc = args[++i];
	    Object o = localeMap.get(loc);
	    if (o instanceof Locale) {
	      locale = (Locale) o;
	    } else {
	      System.err.println("Locale " + loc + 
				 " is not supported. Use the default locale instead.");
	    }
	  } else if ("-config".equals(arg)) {
	    sysConfigFile = args[++i];
	  } else if ("-look-and-feel".equals(arg)) {
	    lnf = args[++i];
	  }
	}
      }
    }
    lookAndFeelName = lnf;

    String javaver = System.getProperty("java.version");
    String javavendor = System.getProperty("java.vendor");
    String javahome = System.getProperty("java.home");
    log.info("Java:  version=" + javaver + 
	     "  vendor=" + javavendor + 
	     "  home="   + javahome);

    String jvmver = System.getProperty("java.vm.version");
    String jvmvendor = System.getProperty("java.vm.vendor");
    String jvmname = System.getProperty("java.vm.name");
    log.info("JVM: version=" + jvmver + 
	     "  vendor=" + jvmvendor + 
	     "  name="   + jvmname);

    String osname = System.getProperty("os.name").toLowerCase();
    String osarch = System.getProperty("os.arch");
    String osver = System.getProperty("os.version");
    log.info("OS: name=" + osname + "  arch=" + osarch + "  version=" + osver);

    String username = System.getProperty("user.name");
    String userhome = System.getProperty("user.home");
    String userdir = System.getProperty("user.dir");
    log.info("User: name=" + username + 
	     "  home=" + userhome + 
	     "  dir="  + userdir);

    isMac = false;
    isLinux = false;
    isWindows = false;
    isWindowsVista = false;
    if (osname.indexOf("mac") != -1) {
      isMac = true;
    } else if (osname.indexOf("linux") != -1) {
      isLinux = true;
    } else if (osname.indexOf("windows") != -1) {
      isWindows = true;
      if (osname.indexOf("windows vista") != -1) {
	isWindowsVista = true;
      }
    }

    initConfig(configRes);
  }

  public static void initGUI() { 
    JDialog.setDefaultLookAndFeelDecorated(true);
    JFrame.setDefaultLookAndFeelDecorated(true);

    String lnf = lookAndFeelName;
    if (lnf == null) { 
      lnf = sysprop.getProperty("LAF");
    }
    lookAndFeelManager = LookAndFeelManager.getInstance();
    LookAndFeelManager.LookAndFeel userLnF = lookAndFeelManager.findLookAndFeel(lnf); 
    if (userLnF != null) { 
      lookAndFeelManager.setLookAndFeel(userLnF); 
    } else { 
      if (!isMac) { 
	lookAndFeelManager.setDefaultLookAndFeel(); 
      } else { 
	log.info("Use Mac native look and feel");
      }
    }
    //Thread.yield(); // yield to Swing EPT to process L&F 

    //setViewLocale(locale);

  }

  ///////////////////////////////////////////////
  //
  // Fields 
  // 
  ///////////////////////////////////////////////

  protected static final boolean debug = true; 

  protected static String appName = null;

  protected static String version = null;

  protected static String revision = null;

  protected static String build = null;

  protected static String releaseDate = null;

  protected static String appDir = null;

  protected String namespaceURI = null;

  protected String namespacePrefix = null;

  protected AppListener appListener = null; 

  protected JFrame frame; 
  
  protected int frameWidth  = 800; // default initial width
  protected int frameHeight = 600; // default initial height

  protected List<Application> apps = new ArrayList<Application>();
  protected Application curApp = null; 

  protected HourGlassPane hourGlassPane = null; 

  // splash screen 
  protected static SplashScreen splashScreen = null;

  // reources and locale 
  protected static String resourceName = "application";

  protected static ResourceManager resourceManager = null;

  protected static Locale locale = null;

  protected static Map<String, Locale> localeMap;

  // OS, looks & feel 
  protected static boolean isMac = false;
  protected static boolean isLinux = false;
  protected static boolean isWindows = false;
  protected static boolean isWindowsVista = false;

  protected static String lookAndFeelName = null;
  protected static LookAndFeelManager lookAndFeelManager;

  // system properties 
  protected static Properties sysprop = new Properties();

  protected static String defaultDir;

  // Logger

  protected static Handler xmlHandler = null;

  protected static Handler simpleHandler = null;

  protected static Logger log;

  protected static final String LOG_DIR = "logs";

  protected static final String LOG_FILE = "avmlog";

  protected static final String LOG_VERSION = "1.0";

  // System configuration file 
  protected static String sysConfigFile = "/config/draw/VisualModelerConfig.xml";


  ///////////////////////////////////////////////
  //
  // Constants
  //    
  ///////////////////////////////////////////////

  public static final Image drawFrameIcon = ResourceLoader.loadImage("draw24");

  // /////////////////////////////////////////////
  //
  // Static fields initialization 
  //    
  // /////////////////////////////////////////////

  static {
    defaultDir = ".";

    localeMap = new HashMap<String, Locale>();
    localeMap.put("default", Locale.getDefault());
    localeMap.put("US", Locale.US);
    localeMap.put("EN", Locale.ENGLISH);
    localeMap.put("CN", Locale.SIMPLIFIED_CHINESE);
    localeMap.put("ZH", Locale.SIMPLIFIED_CHINESE);
    localeMap.put("FR", Locale.FRENCH);
  }

}
